HID の代表と言えば
USBのHID(ヒューマン・インターフェース・デバイス)と言えば、キーボートとマウスが代表的なものです。 まっ親指シフト変換器を作っている関係上キーボードに関してはある程度知識はあります。 HID Keyboardを作ってみてUSB版親指シフト変換器を検討してみます。
回路構成 (2016/01/23 更新)
回路図を実際に作った物に合わせました。(2016/01/23)
今回はキーボートとしての通信部分を構成するだけですので、何時ものPIC18F14K50を使います。 本当のキーボードを作るならキーマトリックスを構成しないといけないので、PIC18F45K50辺りが使いやすいかな?
一応LEDを3個とSIO関連のポートを引き出しています。シリアル(RS232C相当)キーボードかPS2キーボードが繋がるようにしています。
HID プートローダーは、付けることはできますがあえて外してあります。 付けるのも難しくはないです。
プログラム (2016/03/12 更新)
USBのプラグアンドプレイ部分にバグがあり特定の条件で接続までに時間がかかっていました。 それを修正しています
HID Keyboad Demo プログラム
HID Keyboad Demoのhexファイル及びソースファイル一式が入っています。
ソースファイル構成
- keyboard.c
- usbhiddev.c
- usbhiddcv.h
- usbdesc.h
- usbconfig.h
- pic18f14k50_xc8_bootloader.hex
keyboard.cにmainがあります。 またプートローダー用にpic18f14k50_xc8_bootloader.hexを入れていますが使用していません。 使うことはできます。
MicrochipのHID Keyboad Demoと同じように基板上のスイッチを押すたびに、"a","b","c"と文字が出るようにプログラムしています。 一部構造を変更してリピートができるようにしていますので"c"の時押し続けると、"abcccccc"と続けて表示されます。
HID キーボードについて
mainが有るのは、keyboad.cです。 この中でKeyboadの処理をすべて行うことになります。
特殊なキーボードを作る訳ではないので、BootとReportと同じ構成のレポートとしてます。 ここでいうBootとは、PCのBIOSを操作するときにも動作するUSB キーボードを指します。 Reportは、Bootと違うレポート構成にしてもOKですよという事らしいのですが…使っているキーボードはあるのかな?
レポート
レポートとは、キーの押されている状態を送ったり、インディケーター(表示)の状態を受け取ったりするデータのことです。
// キーボード インプット レポート
typedef union _KEY_IN_REPORT {
uint8_t buf[8]; //バッファ
struct {
struct {
unsigned leftControl :1; //左コントロールキー
unsigned leftShift :1; //左シフトキー
unsigned leftAlt :1; //左Altキー
unsigned leftGUI :1; //左GUI(Win/Command)キー
unsigned rightControl :1; //右コントロールキー
unsigned rightShift :1; //右シフトキー
unsigned rightAlt :1; //右Altキー
unsigned rightGUI :1; //右GUI(Win/Command)キー
}bits;
unsigned :8;
uint8_t key[6]; //キーコード
};
} KEY_IN_REPORT;
KEY_IN_REPORT keyInRepote; // 送信バッファ兼用
// キーボード アウトプット レポート
typedef union _KEY_OUT_REPORT {
uint8_t buf[8]; //バッファ
struct {
unsigned numlock :1; //numlock フラグ
unsigned capslock :1; //capslock フラグ
unsigned compose :1; //ScrollLock フラグ
unsigned kana :1; //カナ フラグ
unsigned :4;
}bits; //フラグbits
} KEY_OUT_REPORT;
KEY_OUT_REPORT keyOutRepote; // 受信バッファ兼用
上記は、IN/OUTレポートに合わせた構造体です。 keyInRepote に、キー情報を書いて送り出せばキーボード/パットとして動作します。
送り方は、Windowsの場合500mS毎にMacintoshの場合は32mS毎に送るらしいです。 これは、USB/HIDのIdleの設定値で決まります。初期値を500mSにしておけば、その間隔で keyInRepote を送ります。
キーの状態が変化していない場合は上記でよいのですが、押されたり離されたりしてキーの状態が変わったら、 即座に keyInRepote にキー情報を書き込んで送ります。 間隔は最初(0mS)に戻り次の送信に備えます。
キーリピートについて
PS/2のキースキャンコートを知っているとどの様にキーリピートするか考えますが何も考えなくてよいです。 ホスト側でキーリピート処理をするので、キーの状態を送るだけでOKです。
Microchipのサンプルでは、キーリピートしないようになっています。(キーを押して次に送るレポートは離された状態にしている。) 通常のフルキーボードではリピート有りですが、補助キー入力の場合無い方がよい場合もあります。
ロールオーバーについて
ロールオーバーは、同時に幾つのキーが押されても正常に判断できるかを示します。
キー情報を送れるのが通常キー6個とSHIFT+CTRL+ALT+Win(CMD)です。6ロールオーバー相当になります。 まっ通常で問題になることはないでしょう。
ただPS/2やPC9801,FMTキーボードなどのnロールオーバーと比べると見劣りします。 この辺をReportプロトコルでロールオーバーを増やしたりすると思われます。
さて 問題は、HID Keyboardに離されたと言うBreakCodeに相当するコードがありません。 押されていると言う状態情報しか送りません。通常のキーを7個以上同時押したらどうなるでしょう。
答えは、エラーコードを返します。 ただし6個に戻ったら正常なキー情報返す必要があります。
キーコードの下位からコードで埋めていくのですが、必ずしも押された順番でなくてもよいみたいです。 マトリックス状のキーを順番に確認して押されていればキーコードに書き込んで行き全て確認が終わって 6個以内ならそのキー情報を7個目が見つかったらエラーに書き換えて送るって感じです。
今回のHID Keyboad DEMO にはこの機能は、実装されていません。(エラーにならないから。)
USBの動作
HID サンプルでは書かなかったのですが一応usbdev.cのUSB動作を簡単に書いておきます。 またusbdev.cの少し使いにくかった部分を修正しています。(SOF部分)
- バス接続
- バスリセット (割込)
- バスアイドル (割込)
- バスアクティビリティ (割込)
- バスストール (割込)
- Start of Frame (割込)
- Setup (割込)
- 通信処理
バスパワーの場合は、電源投入を含みUSBケーブルが接続される状態になります。 バスパワーの検出部がある場合は、接続情報が"1"になります。
ホスト側からバスリセットが来て、USBの初期化が行われます。 バスに接続してサスペンド状態以外の時、いつでも受け付けられるようにしておきます。
ホスト側から3mS以上通信が無い場合、このフラグが立ちます。 使わない電力を切り(50μA以下)サスペンドに移行します。 ディスクトップPCの場合、ソフトウェアで電源を切った状態がこれに当たるみたいです。
サスペンドから通常の通信状態に戻る時、このフラグが立ちます。 電源を復帰して通常状態に戻ります。
通信を止める為のフラグが立ちます。 通常は発生しません。
Low/Full Speedでは、1mS毎にフレームを生成しますが、そのフレームの最初を示すフラグです。 割り込みで処理する場合には1mS割り込みになります。 usbdev.cでは、32bitのカウントを行っています。
送信/受信が完了した時に、送信完了フラグが立ちます。
この時、通信がセットアップ・パケットと判断した場合割り込み内で処理を行います。 此処がUSBの少し面倒な部分ですが、殆ど定型処理なので慣れれば大したことはありません。
usbhiddev.cは、スタンダードリクエストとクラスリクエスト(HID)を処理しています。 usbhiddev.cの大部分をこの処理で占めています
通常の通信は、フラグをクリアする処理だけ行います。
送信する場合はUSB用のバッファにデータを転送してSIEに送信処理を行うように伝えます。 受信が完了していま場合はUSB用のバッファからデータを引き取りSIEに受信処理を行うように伝えます。
流れとしては、
(1) バス接続 : (2) バスリセット : (3) Setup (Device Descriptor) : (4) Setup (Set Address) : (5) Setup (Configuration Descriptor) : (6) Setup (String Descriptor) : (7) Setup (Set Configuration) : (8) Setup (Get Report descriptor) : (9) 通常通信 :
と言う感じできます
上記の(7)までは、殆ど変わることが無いと思います。(細かい動作は各ICにより違うようですが) ここまで動かすのに多少苦労することがあります。 (8)は、HIDの初期処理でこの後いくつか入る場合もあります。
(9)以降は、まっ自由に通信ができる状態です。
SIEとはSerial Interface EngineでUSBモジュールの通信処理を行い部分を指しています。
usbhiddev.c の 関数
USB(HID)を操作する関数の使い方を記載します。 送信に多少気を付ける必要がありますが、難しくはないと思います。
初期化
void USBDeviceInit(void)
- 引数
無し
- 戻り値
無し
USBモジュールを使用できるように初期化します。 ただしUSBバス接続処理は行いません。
デバイスタスク
USB_DEVICE_STATE USBDeviceTasks(void)
- 引数
無し
- 戻り値
USB_DEVICE_STATE
DETACHED_STATE | 未接続状態 |
ATTACHED_STATE | 接続状態 |
POWERED_STATE | USB電源投入状態 |
DEFAULT_STATE | USB初期状態状態 |
ADR_PENDING_STATE | ペンディンク状態 |
ADDRESS_STATE | アドレス設定処理 |
CONFIGURED_STATE | 設定完了状態 (通信中) |
メインループに入れて使用します。 戻り値は、USBの通信状態を示します。
割込みタスク
void USBInterruptTasks(void)
- 引数
無し
- 戻り値
無し
割り込み処理に入れて使用します。 High/Low 割込みのどちらでも良いですが、main側でプライオリティを設定します。
void main (void) { : RCONbits.IPEN = 1; // 優先度レベルの設定を有効にす IPR2bits.USBIP = 0; // USB 割り込み優先度:Low : USBDeviceInit(); // 割り込み対応USBの初期化 INTCONbits.GIEL = 1; // 低割り込み許可 INTCONbits.GIEH = 1; // 高割り込み許可 : } void interrupt low_priority IsrLow(void) { // USB割り込み処理 USBInterruptTasks(); }
例. Lowレベル割り込み
EPn受信中フラグ
bool EPnOutIsBusy(uint8_t ep)
- 引数
ep : Endpoint 番号 (1~n)
- 戻り値
true:受信中 / false:受信完了
指定したEndpointが受信中か調べます。 よく使うep=1は、uint8_t HidRxIsBusy(void)としてマクロ化してあります。
EPnリポート受信
uint8_t EPnOutReport(uint8_t ep, uint8_t* pbuf, uint8_t len)
- 引数
ep : Endpoint 番号 (1~n)
*pbuf : レポートバッファポインタ
len : レポートバッファの長さ
- 戻り値
受信バッファの長さ。または、受信情報
RCV_ERROR | 0xFF | 受信エラー |
RCV_RECEING | 0xF0 | 受信中 |
受信バッファサイズ | 8~64 | 受信完了 |
指定したEndpointの受信処理を行います。 よく使うep=1は、uint8_t HidRxReport(uint8_t* pbuf, uint8_t len)としてマクロ化してあります。
EPn送信中フラグ
bool EPnInIsBusy(uint8_t ep)
- 引数
ep : Endpoint 番号 (1~n)
- 戻り値
true:送信中 / false:送信待機(データ待ち)
指定したEndpointが送信中か調べます。 よく使うep=1は、uint8_t HidTxIsBusy(void)としてマクロ化してあります。
EPnリポート送信
uint8_t EPnInReport(uint8_t ep, uint8_t* pram, const uint8_t* prom, uint8_t len, bool standby)
- 引数
ep : Endpoint 番号 (1~n)
*pram : RAMバッファポインタ
*prom : ROMバッファポインタ
len : バッファの長さ
standby : USBバッファ書き込み済みフラグ(内部処理用)
- 戻り値
送信するデータ数。または、送信情報
SND_ERROR | 0xFF | 送信エラー |
SND_STANDBY | 0xF0 | 送信待機 |
送信データ数 | 0~64 | 送信開始 |
指定したEndpointの送信処理を行います。 RAMまたはROMのバッファポインタは、使う側にポインタを代入して使わない側は必ずNULL(0)にしてください。 NULLを判断して使う側を判断します。
よく使うep=1は、下表のようにマクロ化してあります。
RAMから送信 | uint8_t HidTxRamReport(uint8_t* pram, uint8_t len) |
ROMから送信 | uint8_t HidTxRomReport(const uint8_t* prom, uint8_t len) |
SOFカウント
uint32_t USBGetSOFCounter(void)
- 引数
無し
- 戻り値
カウント値(32bit)
USBのStart of Frameをカウントしてその値を返します。 SOFは、1mS間隔で送られてきますので時間制御に使えます。
この関数を使用する場合は、usbconfig.hの中の「 #define ENSBLE_SOF_COUNTER 」を有効にしてください。 (32bit処理は大きくなるので使わない場合は処理を外しています。)
アイドルタイム
uint32_t USBGetIdleTime(void)
- 引数
無し
- 戻り値
アイドル値(mS単位)
HIDのアイドルレートをmS単位に換算して返します。 Windowsでは、500mS Macintoshでは、32mSと言われています。(確認していません。)
Keyboardの通常通信間隔になりますす。
アクティブプロトコル
uint8_t USBGetActiveProtocol(void)
- 引数
無し
- 戻り値
プロトコル (0:boot / 1:Report)
HIDの現在使われているプロトコルを返します。
Keyboardのプロトコル設定になります。
免 責
情報は出来るだけ正確に書くつもりです。ただこの記事を見て作ると思ったときは、 個人の責任において作業を行なってください。 データの喪失や機器の損傷が有っても、一切の責任は取れません。